package com.example.android.worldracers;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.LinkedList;
import java.util.Random;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.SurfaceHolder;

class RacersThread extends Thread {

	
	/** Handle to the application context, used to e.g. fetch Drawables. */
	private Context mContext;
	
    // Describes the image format our bitmaps should be converted to.
    private static BitmapFactory.Options sBitmapOptions 
        = new BitmapFactory.Options();
	
	// State-tracking constants
	public static final int STATE_LOSE = 1;
	public static final int STATE_PAUSE = 2;
	public static final int STATE_RUNNING = 4;
	public static final int STATE_WIN = 5;

	// Current height/width of the surface/canvas.
	private int mCanvasHeight = 200;
	private int mCanvasWidth = 320;
	
	// Message handler used by thread to interact with TextView
	private Handler mHandler;

	// The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN
	private int mMode;

	// Indicate whether the surface has been created & is ready to draw/
	private boolean mRun = false;

	// Handle to the surface manager object we interact with
	private SurfaceHolder mSurfaceHolder;
	
	//Game specific members
	private Bitmap mControlsBitmap;
	private Bitmap mSpritesBitmap;
	private Bitmap mSprites2Bitmap;
	private Bitmap mBgBitmap;
	private Rect mControlsRect;
	
	private Rect mBackgroundSrcRect;
	
	private Rect mPlayerCarForwardSrcRect;
	private Rect mPlayerCarLeftSrcRect;
	private Rect mPlayerCarRightSrcRect;	
	private Rect mRivalCarForwardSrcRect;
	private Rect mRivalCarLeftSrcRect;
	private Rect mRivalCarRightSrcRect;	
	
	private RoadSprite mLastRoadElementSprite;
	
	private Rect mTreeSrcRect;
	private Rect mRockSrcRect;
	
    private static String[] heightStrs = new String[] {"Flat","Up","Down"};
    private static String[] curveStrs = new String[] {"Straight","Left","Right"};

	private Paint mSimplePaint;	
	private int mRaceNumber = 1;
	private int mRacePosition = 1;
	private int mNumberOfRacers = 2;
	private Random mRandom;
	private Date mStartTime;
	private double  mLastPlayerDelta = 0;
	private String  mCurrentTimeString = "";
	private String  mCurrentSpeedString = "";
	
	private RoadParam mRoadParam;	
	private LinkedList<RoadElement> mRoad;
	private int[] mZoneCurves;
	private int[] mZoneHeights;
	private Render mRender;
	private Racer mPlayer;
	private Racer mRival;
	

	/***
	 * The main RacersActivity thread
	 * 
	 * @param surfaceHolder
	 * @param context
	 * @param handler
	 */
	public RacersThread(SurfaceHolder surfaceHolder, Context context,int startLevelNumber,
			Handler handler) {
		
		Log.w(this.getClass().getName(), "RacersThread.ctor");
		
		// get handles to some important objects
		mSurfaceHolder = surfaceHolder;
		mHandler = handler;
		mContext = context;
		
		mRoadParam = new RoadParam();
		mRender = new Render();
		
		mPlayer = new Racer();		
		mRival = new Racer();
		//Adjust the start positions so the cars are on different sides of the screen
		//mPlayer.posx += 50;
		//mRival.posx -= 50;
		mRival.increasePosition(10);
		
        // Sets our preferred image format to 16-bit, 565 format.
        sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        
        //Load the controls image and store its position
        mControlsBitmap = loadBitmap(mContext, R.drawable.btn_zoom_page_normal);
        //left top right bottom
        int controlsHeight = mControlsBitmap.getHeight();
        int controlsWidth = mControlsBitmap.getWidth();
        mControlsRect = new Rect(0,mCanvasHeight - controlsHeight ,controlsWidth,mCanvasHeight);
        
        //Setup a simple paint object to use for our sprite drawings
        mSimplePaint = new Paint();
        mSimplePaint.setAntiAlias(false);
        mSimplePaint.setDither(false);
        mSimplePaint.setFilterBitmap(false);
        
        //Load the sprites and setup the source rectangles
        mSpritesBitmap = loadBitmap(mContext, R.drawable.spritesheethigh);
        mSprites2Bitmap = loadBitmap(mContext, R.drawable.spritesheet2);
        mBgBitmap = loadBitmap(mContext,R.drawable.bg1);
        //left top right bottom
        mBackgroundSrcRect = new Rect(0,9,640,120);        
        
    	mPlayerCarForwardSrcRect = new Rect(0,130,69,168);
    	mPlayerCarLeftSrcRect = new Rect(70,130,147,168); //car_4
    	mPlayerCarRightSrcRect = new Rect(148,130,225,168); //car_8
    	
    	mRivalCarForwardSrcRect = new Rect(0,130,69,168);
    	mRivalCarLeftSrcRect = new Rect(70,130,147,168); //car_4
    	mRivalCarRightSrcRect = new Rect(148,130,225,168); //car_8
    	
    	mTreeSrcRect = new Rect(321,9,344,59);
    	mRockSrcRect = new Rect(345,9,356,23);
    	
    	mRandom = new Random();
    	mStartTime = new Date();
    	
    	//Generate the level
        generateRoad();
        
	}
	
	/**
	 * Generates the road randomly 
	 */
    private void generateRoad() {	
        
    	mRoad = new LinkedList<RoadElement>();
    	mZoneCurves = new int[mRoadParam.getLength()];
    	mZoneHeights = new int[mRoadParam.getLength()];
    	
    	int currentStateH = 0; //0=flat 1=up 2= down
        int[][] transitionH = new int[][] {{0,1,2},{0,2,2},{0,1,1}};
        
        int currentStateC = 0; //0=straight 1=left 2= right
        int[][] transitionC = new int[][] {{0,1,2},{0,2,2},{0,1,1}};

        double currentHeight = 0;
        double currentCurve = 0;

        for(int zones = mRoadParam.getLength();zones > 0;zones--) {
            // Generate current Zone
            double finalHeight = 0;
            switch(currentStateH){
                case 0:
                    finalHeight = 0; break;
                case 1:
                    finalHeight = RoadParam.MAX_HEIGHT * mRandom.nextDouble(); break;
                case 2:
                    finalHeight = -RoadParam.MAX_HEIGHT * mRandom.nextDouble(); break;
            }
            double finalCurve = 0;
            switch(currentStateC){
                case 0:
                    finalCurve = 0; break;
                case 1:
                    finalCurve = -RoadParam.MAX_CURVE * mRandom.nextDouble(); break;
                case 2:
                    finalCurve = RoadParam.MAX_CURVE * mRandom.nextDouble(); break;
            }
            
            mZoneHeights[zones-1] = currentStateH;
            mZoneCurves[zones-1] = currentStateC;
            
            //zone range is currentHeight to finalHeight
            double zoneHeightChange = finalHeight - currentHeight;
            double stepHeightSize = zoneHeightChange/(double)RoadParam.ZONE_SIZE;
            
            double zoneCurveChange = finalCurve - currentCurve;
            double stepCurveSize = zoneCurveChange/(double)RoadParam.ZONE_SIZE;
            
            double segmentHeight = currentHeight;
            double segmentCurve = currentCurve;
            for(int i=0; i < RoadParam.ZONE_SIZE; i++){
            	RoadSprite sprite;
            	
                // add a rock every 50 segments
                if(i % RoadParam.ZONE_SIZE / 5 == 0){
                	//always on the right
                    sprite = new RoadSprite(RoadSprite.OBJ_ROCK, -0.55);
                } else {
                	//sometimes add a tree
                    if(mRandom.nextDouble() < 0.05) {   
                    	//add it on the left
                        sprite = new RoadSprite(RoadSprite.OBJ_TREE, 0.6 + 4*mRandom.nextDouble());    
                        //half the time add them on the right instead 
                        if(mRandom.nextDouble() < 0.5){
                        	sprite.negatePosition();
                        }
                    } else {
                    	sprite = new RoadSprite(RoadSprite.OBJ_NONE);
                    }
                }

            	segmentHeight += stepHeightSize;
            	segmentCurve += stepCurveSize;
            	
            	//Log.w(this.getClass().getName(), "Zone " + zones + " Road Height " + segmentHeight + " Curve " + segmentCurve);

                mRoad.addLast(new RoadElement(segmentHeight,segmentCurve,sprite));
            	
            }
            currentHeight = segmentHeight; //should be equal to finalHeight by this point
            currentCurve = segmentCurve; //should be equal to finalCurve by this point
            //The next zone will then start at the correct height
            
            // Find next zone
            if(mRandom.nextDouble() < RoadParam.MOUNTAINY){
            	//currentStateH = transitionH[currentStateH][1+Math.round(r())];
                currentStateH = transitionH[currentStateH][(int) (1+Math.round(mRandom.nextDouble()))];
            } else {
                currentStateH = transitionH[currentStateH][0];
            }
            if(mRandom.nextDouble() < RoadParam.CURVY){
            	//currentStateC = transitionC[currentStateC][1+Math.round(r())];
                currentStateC = transitionC[currentStateC][(int) (1+Math.round(mRandom.nextDouble()))];
            } else {
                currentStateC = transitionC[currentStateC][0];
            }
        }//for zones-- > 0
        mRoadParam.setLength(mRoadParam.getLength() * RoadParam.ZONE_SIZE);
        
        mRacePosition = mNumberOfRacers;      
        
        //Put the rival car in the first segment of the road
//        RoadElement re = mRoad.getFirst();
//        mLastRoadElementSprite = re.getSprite();
        
        mLastRoadElementSprite = new RoadSprite(RoadSprite.OBJ_NONE,0.0);
        
        //Create roadSprite for the rival
//        RoadSprite rrs = new RoadSprite(RoadSprite.OBJ_RIVAL,mRival.rivalStartPosx);
//        re.setSprite(rrs);
//        mRoad.set(0,re);
	}
    
    public void drawImage(Canvas c, Bitmap bitmap, Rect srcImageRect, int x, int y, double scale){    	
    	Rect dstRect = new Rect(x,y,(int)(x+(scale*srcImageRect.width())),(int)(y+(scale*srcImageRect.height())));    	
    	c.drawBitmap(bitmap,srcImageRect,dstRect,mSimplePaint);    	
    }

    public void drawSprite(Canvas c, SpriteBufferElement sprite){

    		Bitmap bitmap = mSpritesBitmap;
    		//The JS version nicely uses sprite.i as tree or rock
    		Rect srcRect;
            switch(sprite.getType()) {
	            case RoadSprite.OBJ_ROCK:
	            	srcRect = mRockSrcRect;
	            	break;
	            case RoadSprite.OBJ_TREE:
	            	srcRect = mTreeSrcRect;
	            	break;
	            case RoadSprite.OBJ_RIVAL:	            	
	            	bitmap = mSprites2Bitmap;
	            	//Use the left/right sprite if on a curve
	            	if(mRival.getTurning() == Racer.TURN_LEFT) {
	            		srcRect = mRivalCarLeftSrcRect;
	            	} else if(mRival.getTurning() == Racer.TURN_RIGHT) {
	            		srcRect = mRivalCarRightSrcRect;
	            	} else { //on a straight
	            		srcRect = mRivalCarForwardSrcRect;
	            	}
	            	break;
	            default:
	            	return;
            }  	
    	
    		double destY = sprite.mY - srcRect.height() * sprite.mScale;
            
            double h = srcRect.height();            
            if(sprite.mYMax < sprite.mY) {
                h = Math.min(srcRect.height() * (sprite.mYMax - destY) / (srcRect.height() * sprite.mScale), srcRect.height());
                //adjust the height of the src sprite if necessary
                srcRect.bottom = (int) (srcRect.top + h);
            } 
            
            Rect destRect = new Rect((int)sprite.mX,
            		(int)destY,
            		(int)(sprite.mX + (sprite.mScale * srcRect.width())),
            		(int)(destY + (sprite.mScale * h)));
            

            if(h > 0) {
            	c.drawBitmap(bitmap,  srcRect, destRect, mSimplePaint);
            }
    }
    
    private void drawSegment(Canvas c, double position1, double scale1, double offset1, double position2, double scale2, double offset2, boolean alternate, boolean finishStart){
   	
    	//More desert sand color than gras...
    	String grassColor = "#ddcc99"; //SAND
    	
    	String borderColor = "#ffffff"; //white
        String roadColor = "#777777"; //DARK GRAY
        String laneColor = "#777777"; //DARK GRAT
        if(alternate) {
        	grassColor = "#eeddaa";  //SAND
        	borderColor = "#ee0000"; //RED
        	roadColor = "#999999"; //LT GRAY
        	laneColor = "#ffffff"; //WHITE
        }
        if(finishStart) { //white
        	borderColor = "#ffffff";
        	roadColor = "#ffffff";
        	laneColor = "#ffffff";
        }

        //draw grass:
        Paint segmentPaint = new Paint();       
        segmentPaint.setColor(Color.parseColor(grassColor));       
        c.drawRect(0, (float)position2, (float)mRender.getWidth(),(float)position1, segmentPaint);
        
        // draw the road
        segmentPaint.setColor(Color.parseColor(roadColor)); 
        drawTrapez(c, position1, scale1, offset1, position2, scale2, offset2, -0.5, 0.5, segmentPaint);
        
        //draw the road border
        segmentPaint.setColor(Color.parseColor(borderColor)); 
        drawTrapez(c, position1, scale1, offset1, position2, scale2, offset2, -0.5, -0.47, segmentPaint);
        drawTrapez(c, position1, scale1, offset1, position2, scale2, offset2, 0.47,   0.5, segmentPaint);
        
        // draw the lane line
        segmentPaint.setColor(Color.parseColor(laneColor)); 
        drawTrapez(c, position1, scale1, offset1, position2, scale2, offset2, -0.18, -0.15, segmentPaint);
        drawTrapez(c, position1, scale1, offset1, position2, scale2, offset2,  0.15,  0.18, segmentPaint);
    }
    
    public void drawTrapez(Canvas c, double pos1, double scale1, double offset1, double pos2, double scale2, double offset2, double delta1, double delta2, Paint paint){
        int demiWidth = mRender.getWidth() / 2;

        Path p = new Path();
        paint.setStyle(Paint.Style.FILL);

        float x1 = (float)(demiWidth + delta1 * mRender.getWidth() * scale1 + offset1);
        p.moveTo(x1, (float) pos1);
        
        float x2 = (float)(demiWidth + delta1 * mRender.getWidth() * scale2 + offset2);
        p.lineTo(x2, (float) pos2); 
        
        float x3 = (float)(demiWidth + delta2 * mRender.getWidth() * scale2 + offset2);
        p.lineTo(x3, (float) pos2); 
        
        float x4 = (float)(demiWidth + delta2 * mRender.getWidth() * scale1 + offset1);
        p.lineTo(x4, (float) pos1);
        
        p.close();
        
        c.drawPath(p, paint);
        
        p.reset();
    }
    
    public void drawBackground(Canvas c, Bitmap b, int position) {
        int first = position / 2 % (mBackgroundSrcRect.width());
        drawImage(c, b, mBackgroundSrcRect, first-mBackgroundSrcRect.width() +1, 0, 1);
        drawImage(c, b, mBackgroundSrcRect, first+mBackgroundSrcRect.width() -1, 0, 1);
        drawImage(c, b, mBackgroundSrcRect, first, 0, 1);
    }
        
    public void drawString(Canvas c,String string, int x,int y) {
        string = string.toUpperCase();
        int cur = x;
        for(int i=0; i < string.length(); i++) {
        	//Converts the char to int to get position (A is 65)
        	int left = ((int)string.charAt(i) - 32) * 8;
        	//left top right bottom
        	Rect srcRect = new Rect(left,0,left+8,8);
        	Rect dstRect = new Rect(cur,y,cur + 8,y+8);
        	
        	c.drawBitmap(mSpritesBitmap, srcRect, dstRect, mSimplePaint);

            cur += 8;
        }
    }


	/**
     * Loads a bitmap from a resource and converts it to a bitmap.  This is
     * a much-simplified version of the loadBitmap() that appears in
     * SimpleGLRenderer.
     * @param context  The application context.
     * @param resourceId  The id of the resource to load.
     * @return  A bitmap containing the image contents of the resource, or null
     *     if there was an error.
     */
    protected Bitmap loadBitmap(Context context, int resourceId) {
        Bitmap bitmap = null;
        if (context != null) {
          
            InputStream is = context.getResources().openRawResource(resourceId);
            try {
                bitmap = BitmapFactory.decodeStream(is, null, sBitmapOptions);
            } finally {
                try {
                    is.close();
                } catch (IOException e) {
                    // Ignore.
                }
            }
        }

        return bitmap;
    }

	/**
	 * Starts the game, setting parameters for the current difficulty.
	 */
	public void restart() {
		synchronized (mSurfaceHolder) {
			
			Log.w(this.getClass().getName(), "RacersThread.restart");

			setState(STATE_RUNNING);
		}
	}

	/**
	 * Pauses the physics update & animation.
	 */
	public void pause() {
		synchronized (mSurfaceHolder) {
			if (mMode == STATE_RUNNING) {
				setState(STATE_PAUSE);
			}
		}
	}

	/**
	 * Restores game state from the indicated Bundle. Typically called when
	 * the Activity is being restored after having been previously
	 * destroyed.
	 * 
	 * @param savedState
	 *            Bundle containing the game state
	 */
	public synchronized void restoreState(Bundle savedState) {
		synchronized (mSurfaceHolder) {
			setState(STATE_PAUSE);
		}
	}
	
	/**
	 * Dump game state to the provided Bundle. Typically called when the
	 * Activity is being suspended.
	 * 
	 * @return Bundle with this view's state
	 */
	public Bundle saveState(Bundle map) {
		synchronized (mSurfaceHolder) {
			if (map != null) {
			}
		}
		return map;
	}

	/***
	 * Thread run override - lock the canvas, update physics and draw the
	 * screen Main game loop
	 */
	@Override
	public void run() {
		Log.w(this.getClass().getName(), "RacersThread.run before loop");
		while (mRun) {
			Canvas c = null;
			try {
				c = mSurfaceHolder.lockCanvas(null);
				synchronized (mSurfaceHolder) {
					if (mMode == STATE_RUNNING) {
						updatePhysics();
						renderGameFrame(c);
					}
				}
			} finally {
				// do this in a finally so that if an exception is thrown
				// during the above, we don't leave the Surface in an
				// inconsistent state
				if (c != null) {
					mSurfaceHolder.unlockCanvasAndPost(c);
				}
			}
		}
	}

	
	private void updatePhysics() {
		//Need to check for end in a method that is not updating the canvas,
		//or the canvas will flash behing the dialog box when the game is over
        double absoluteIndex = Math.floor(mPlayer.getPosition() / RoadParam.ROAD_SEGMENT_SIZE);
        
        //Check if the player has crossed the finished line
        if(absoluteIndex >= mRoadParam.getLength()-Render.DEPTH_OF_FIELD-1){
        	nextLevel();
        	return;
        }
        
        //Check if the rival has crossed the finish line
        double rivalAbsoluteIndex = Math.floor(mRival.getPosition() / RoadParam.ROAD_SEGMENT_SIZE);
        
        if(rivalAbsoluteIndex >= mRoadParam.getLength()-Render.DEPTH_OF_FIELD-1){
        	nextLevel();
        	return;
        }
        
        
	}

	/*
	 * Renders one frame
	 */
	private void renderGameFrame(Canvas c) {
        	
		// Clean screen
        c.drawColor(Color.parseColor("#ddcc99"));
        
        // --------------------------
        // -- Update the car state --
        // --------------------------
        
        if (Math.abs(mLastPlayerDelta) > Render.OFF_TRACK_DIST){
        	//Slow down the car if it has gone off the track
            if (mPlayer.getSpeed() > Racer.PLAYER_MAX_SPEED_ON_GRASS) { //40mph
                mPlayer.reduceSpeed(0.2);
            }
        } else {
            // read acceleration controls
            if (mPlayer.getAccelerating() == Racer.ACCELERATE) { // up
                mPlayer.increaseSpeed(mPlayer.acceleration);
            } else if (mPlayer.getAccelerating() == Racer.DECCELERATE) { // down
                mPlayer.reduceSpeed(mPlayer.breaking);
            } else { //not pressing, so slow down
                //mPlayer.speed -= mPlayer.deceleration;
            	//removed to keep speed constant so dont need to press it
            }
        }
        mPlayer.setSpeed(Math.max(mPlayer.getSpeed(), 0)); //cannot go in reverse
        mPlayer.setSpeed(Math.min(mPlayer.getSpeed(), Racer.PLAYER_MAX_SPEED)); //maximum speed
        mPlayer.increasePosition(mPlayer.getSpeed());
        
        Rect carSpriteRectToUse = mPlayerCarForwardSrcRect;
        int carSpriteX = (int) ((mRender.getWidth() / 2) - (mPlayerCarForwardSrcRect.width() /2));
        //The height of all forward/left/right sprites is the same, so we dont need to adjust it
        int carSpriteY = mRender.getHeight() - mPlayerCarForwardSrcRect.height() - 10;
        
        // car turning
        if (mPlayer.getTurning() == Racer.TURN_LEFT) {
            // left
            if(mPlayer.getSpeed() > 0){
                mPlayer.reducePosX(mPlayer.turningAmount);
            }            
            carSpriteRectToUse = mPlayerCarLeftSrcRect;
            carSpriteX = (int) ((mRender.getWidth() / 2) - (mPlayerCarLeftSrcRect.width()/2));
        } else if (mPlayer.getTurning() == Racer.TURN_RIGHT) {
            // 39 right
            if(mPlayer.getSpeed() > 0){
                mPlayer.increasePosX(mPlayer.turningAmount);
            }            
            carSpriteRectToUse = mPlayerCarRightSrcRect;
            carSpriteX = (int) ((mRender.getWidth() / 2) - (mPlayerCarRightSrcRect.width()/2));
        }
        
        // --------------------------
        // -- Update the rival car state --
        // --------------------------
        

         //work out what section of mRoad the rival is in before moving
         double rivalAbsoluteIndex = Math.floor(mRival.getPosition() / RoadParam.ROAD_SEGMENT_SIZE);         
         double rivalOldSegmentIndex    = (rivalAbsoluteIndex - 2) % mRoad.size();
         RoadElement rivalOldSegment         = mRoad.get((int)rivalOldSegmentIndex);
         
         //Move the rival position up
         //TODO accelerate and turn depending on zone curve
         
         
         //Rival car logic is
         //1) on a straight, accelerate upto maximum speed
         //2) on a curve, break until maxCornerSpped
         int rivalZone = (int) (rivalAbsoluteIndex/RoadParam.ZONE_SIZE);
         rivalZone +=1;
         int rivalCurve = mZoneCurves[12-rivalZone];
         //0=straight 1=left 2= right
         if(rivalCurve == 0) {
        	 mRival.setTurning(Racer.TURN_NONE);
        	 mRival.increaseSpeed(mRival.acceleration);
        	 mRival.setSpeed(Math.min(mRival.getSpeed(), Racer.RIVAL_MAX_SPEED));
         } else if(rivalCurve == 1) { //left
        	 mRival.setTurning(Racer.TURN_LEFT);
        	 mRival.reduceSpeed(mRival.breaking);
        	 mRival.setSpeed(Math.max(mRival.getSpeed(), Racer.RIVAL_MAX_CORNER_SPEED));
         } else { //right
        	 mRival.setTurning(Racer.TURN_RIGHT);
        	 mRival.reduceSpeed(mRival.breaking);
        	 mRival.setSpeed(Math.max(mRival.getSpeed(), Racer.RIVAL_MAX_CORNER_SPEED));
         }
         mRival.increasePosition(mRival.getSpeed());
         
         //Add some random movement on the rival xposition
         double rndXMove = mRandom.nextDouble() * 0.01;         
         if(mRandom.nextDouble() < 0.5) {         
        	 mRival.increasePosX(rndXMove);
         } else {
        	 mRival.reducePosX(rndXMove);
         }

         //Work out what segment he is in now
         double rivalNextAbsoluteIndex = Math.floor(mRival.getPosition() / RoadParam.ROAD_SEGMENT_SIZE);         
         double rivalNewSegmentIndex    = (rivalNextAbsoluteIndex - 2) % mRoad.size();
         RoadElement rivalNewSegment         = mRoad.get((int)rivalNewSegmentIndex);
         
         //if he has moved to a new segment, then swap the elements so rival now in its correct segment location
         if((int)rivalOldSegmentIndex != (int)rivalNewSegmentIndex) {      
        	 
        	 //set the old one back to the last sprite
        	 rivalOldSegment.setSprite(mLastRoadElementSprite);        	 
        	 mRoad.set((int)rivalOldSegmentIndex,rivalOldSegment);
        	//store the sprite from the new one as last sprite
        	 mLastRoadElementSprite  = rivalNewSegment.getSprite();
        	 //set the new one one to OBJ_RIVAL
        	 rivalNewSegment.setSprite(new RoadSprite(RoadSprite.OBJ_RIVAL,mRival.getPosX()));
        	 mRoad.set((int)rivalNewSegmentIndex, rivalNewSegment);
        	 
         }
         double rivalAbsIndex = Math.floor(mRival.getPosition() / RoadParam.ROAD_SEGMENT_SIZE);
        
        if(mPlayer.getPosition() >= mRival.getPosition()) {
        	mRacePosition = 1;
        } else {
        	mRacePosition = 2;
        }      

        // --------------------------
        // -- Draw the background
        // --------------------------
        drawBackground(c, mBgBitmap, (int)-mPlayer.getPosX());

        // --------------------------
        // --   Render the road    --
        // --------------------------
        LinkedList<SpriteBufferElement> spriteBuffer = new LinkedList<SpriteBufferElement>();
        
        double absoluteIndex = Math.floor(mPlayer.getPosition() / RoadParam.ROAD_SEGMENT_SIZE);
                
        double currentSegmentIndex    = (absoluteIndex - 2) % mRoad.size();
        double currentSegmentPosition = (absoluteIndex - 2) * RoadParam.ROAD_SEGMENT_SIZE - mPlayer.getPosition();
        RoadElement currentSegment         = mRoad.get((int)currentSegmentIndex);
        
        double lastProjectedHeight     = Double.POSITIVE_INFINITY;
        double counter                 = absoluteIndex % (2 * RoadParam.NUMBER_OF_SEGMENTS_PER_COLOR); // for alternating color band
        
        double playerPosSegmentHeight     = mRoad.get((int) (absoluteIndex % mRoad.size())).getHeight();
        double playerPosNextSegmentHeight = mRoad.get((int) ((absoluteIndex + 1) % mRoad.size())).getHeight();
        double playerPosRelative          = (mPlayer.getPosition() % RoadParam.ROAD_SEGMENT_SIZE) / RoadParam.ROAD_SEGMENT_SIZE;
        double playerHeight               = Render.CAMERA_HEIGHT + playerPosSegmentHeight + (playerPosNextSegmentHeight - playerPosSegmentHeight) * playerPosRelative;
        
        double baseOffset                 =  currentSegment.getCurve() + (mRoad.get((int) ((currentSegmentIndex + 1) % mRoad.size())).getCurve() - currentSegment.getCurve()) * playerPosRelative;
        
        mLastPlayerDelta = mPlayer.getPosX() - baseOffset*2;
        
        for(int iter = Render.DEPTH_OF_FIELD;iter > 0;iter--) {
            // Next Segment:
        	double nextSegmentIndex       = (currentSegmentIndex + 1) % mRoad.size();
            RoadElement nextSegment    = mRoad.get((int) nextSegmentIndex);
            
            double startProjectedHeight = Math.floor((playerHeight - currentSegment.getHeight()) * Render.CAMERA_DISTANCE / (Render.CAMERA_DISTANCE + currentSegmentPosition));
            double startScaling         = 30 / (Render.CAMERA_DISTANCE + currentSegmentPosition);
        
            double endProjectedHeight   = Math.floor((playerHeight - nextSegment.getHeight()) * Render.CAMERA_DISTANCE / (Render.CAMERA_DISTANCE + currentSegmentPosition + RoadParam.ROAD_SEGMENT_SIZE));
            double endScaling           = 30 / (Render.CAMERA_DISTANCE + currentSegmentPosition + RoadParam.ROAD_SEGMENT_SIZE);

            //min returns the smallest number of the parameters
            double currentHeight        = Math.min(lastProjectedHeight, startProjectedHeight);
            double currentScaling       = startScaling;
            
            if(currentHeight > endProjectedHeight){
                drawSegment(
                	c,
                    mRender.getHeight() / 2 + currentHeight, //pos1
                    currentScaling,  //scale1
                    currentSegment.getCurve() - baseOffset - mLastPlayerDelta * currentScaling, //offset1 
                    mRender.getHeight() / 2 + endProjectedHeight,  //pos2
                    endScaling, //scale2
                    nextSegment.getCurve() - baseOffset - mLastPlayerDelta * endScaling, //offset2 
                    counter < RoadParam.NUMBER_OF_SEGMENTS_PER_COLOR, //alternate
                    currentSegmentIndex == 2 || currentSegmentIndex == (mRoadParam.getLength()-Render.DEPTH_OF_FIELD)); //finishStart
            }
            if(currentSegment.getSprite().getType() != RoadSprite.OBJ_NONE){ //!= false
                spriteBuffer.addLast(new SpriteBufferElement(
                    mRender.getHeight() / 2 + startProjectedHeight, 
                    mRender.getWidth() / 2 - currentSegment.getSprite().getPosition() * mRender.getWidth() * currentScaling + currentSegment.getCurve() - baseOffset - (mPlayer.getPosX() - baseOffset*2) * currentScaling,
                    mRender.getHeight() / 2 + lastProjectedHeight, 
                    2.5*currentScaling, 
                    currentSegment.getSprite().getType()));
            }
            
            
            lastProjectedHeight    = currentHeight;

            currentSegmentIndex    = nextSegmentIndex;
            currentSegment         = nextSegment;
            
            currentSegmentPosition += RoadParam.ROAD_SEGMENT_SIZE;
            
            counter = (counter + 1) % (2 * RoadParam.NUMBER_OF_SEGMENTS_PER_COLOR);
        }//for iter
        
        while(!spriteBuffer.isEmpty()) {
        	drawSprite(c, spriteBuffer.removeLast());
        }
                
        // --------------------------
        // --     Draw the cars     --
        // --------------------------
        
        //Always draw the players car
        drawImage(c, mSpritesBitmap, carSpriteRectToUse, carSpriteX, carSpriteY, 1);

        // --------------------------
        // --     Draw the hud     --
        // --------------------------        

        //Race number and percent complete
        drawString(c,"race " + mRaceNumber, mRender.getWidth()/2,1);
        drawString(c,""+Math.round(absoluteIndex/(mRoadParam.getLength()-Render.DEPTH_OF_FIELD)*100)+"%",mRender.getWidth()/2, 10);
        
        //Race position
        drawString(c,"pos "+mRacePosition+"/"+mNumberOfRacers, mRender.getWidth()-8*9,1);
        drawString(c,"Player " + absoluteIndex, mRender.getWidth()-8*16,10);
        drawString(c,"Rival  " + rivalAbsIndex, mRender.getWidth()-8*16,19);
        
        //Lap Time and speed - dont update if we are paused
        if(mMode == STATE_RUNNING) {
	        Date now = new Date();
	        long diff = now.getTime() - mStartTime.getTime();
	        
	        double min = Math.floor(diff / 60000);
	        
	        //Pad with zeros if less than ten
	        double sec = Math.floor((diff - min * 60000) / 1000);
	        String SecStr = String.valueOf(sec);
	        if(sec < 10) {
	        	SecStr = "0" + sec;
	        }
	        
	        //Pad with zeros if less than 100
	        double mili = Math.floor(diff - min * 60000 - sec * 1000);
	        String miliStr = String.valueOf(mili);
	        if(mili < 10) {
	        	miliStr = "00" + mili;
	        } else if(mili >= 10 && mili < 100) {
	        	miliStr = "0" + mili;
	        }
	        
	        mCurrentTimeString = ""+min+":"+SecStr+":"+miliStr;
	        
	        
	        int speedStr = (int) Math.round(mPlayer.getSpeed() / Racer.PLAYER_MAX_SPEED * Racer.PLAYER_MAX_SPEED_MPH);
	        mCurrentSpeedString = ""+speedStr+" mph";
        }
        
        drawString(c,mCurrentTimeString, 1, 1);
        drawString(c,mCurrentSpeedString, 1,10);
        
        int zone = (int) (absoluteIndex/RoadParam.ZONE_SIZE);
        zone +=1;
        //zones drawn last to first, so index is mRoadParam.Length/mRoadParam.zoneSize;
        drawString(c,"Zone "+zone+" Curve " + curveStrs[mZoneCurves[12-zone]]+ " Height "+heightStrs[mZoneHeights[12-zone]], 1,19);
        
        // --------------------------
        // -- Draw the controls    --
        // -------------------------- 
		// Draw the controls at bottom-left of screen
		c.drawBitmap(mControlsBitmap, null, mControlsRect, mSimplePaint);
	}

	/**
	 * Used to signal the thread whether it should be running or not.
	 * Passing true allows the thread to run; passing false will shut it
	 * down if it's already running. Calling start() after this was most
	 * recently called with false will result in an immediate shutdown.
	 * 
	 * @param b
	 *            true to run, false to shut down
	 */
	public void setRunning(boolean b) {
			mRun = b;
			Log.w(this.getClass().getName(), "RacersThread.setRunning called");
	}

	/**
	 * Sets the game mode. That is, whether we are running, paused, in the
	 * failure state, in the victory state, etc.
	 * 
	 * @see #setState(int, CharSequence)
	 * @param mode
	 *            one of the STATE_* constants
	 */
	public void setState(int mode) {
		synchronized (mSurfaceHolder) {
			setState(mode, null);
		}
	}

	/**
	 * Sets the game mode. That is, whether we are running, paused, in the
	 * failure state, in the victory state, etc.
	 * 
	 * @param mode
	 *            one of the STATE_* constants
	 * @param message
	 *            string to add to screen or null
	 */
	public void setState(int mode, CharSequence message) {
		/*
		 * This method optionally can cause a text message to be displayed
		 * to the user when the mode changes. Since the View that actually
		 * renders that text is part of the main View hierarchy and not
		 * owned by this thread, we can't touch the state of that View.
		 * Instead we use a Message + Handler to relay commands to the main
		 * thread, which updates the user-text View.
		 */
		synchronized (mSurfaceHolder) {
			mMode = mode;
		}
	}

	/***
	 * Callback invoked when the surface dimensions change.
	 */
	public void setSurfaceSize(int width, int height) {
		
		if(width != mCanvasWidth || height != mCanvasHeight) {			
			
			Log.w(this.getClass().getName(), "RacersThread.setSurfaceSize called, has changed");
			// synchronized to make sure these all change atomically
			synchronized (mSurfaceHolder) {
				
				//This must be an even number, of the ANGLE constants will
				//not be correct!
				mCanvasWidth = width;
				if(mCanvasWidth %2 != 0) {
					mCanvasWidth--;
				}				
				mCanvasHeight = height;
				
		        //left top right bottom
		        int controlsHeight = mControlsBitmap.getHeight();
		        int controlsWidth = mControlsBitmap.getWidth();
		        mControlsRect = new Rect(0,mCanvasHeight - controlsHeight ,controlsWidth,mCanvasHeight);
		        
		        mRender.update(width,height);
			}
		}
	}

	/**
	 * Resumes from a pause.
	 */
	public void unpause() {
//		// Move the real time clock up to now
//		synchronized (mSurfaceHolder) {
//			mLastTime = System.currentTimeMillis() + 100;
//		}
		setState(STATE_RUNNING);
	}

	/**
	 * Handles a key-down event.
	 * 
	 * @param keyCode
	 *            the key that was pressed
	 * @param msg
	 *            the original event object
	 * @return true
	 */
	boolean doKeyDown(int keyCode, KeyEvent msg) {
		//synchronized (mSurfaceHolder) {
			boolean okStart = false;
			if (keyCode == KeyEvent.KEYCODE_DPAD_UP)
				okStart = true;
			if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
				okStart = true;
			if (keyCode == KeyEvent.KEYCODE_S)
				okStart = true;

			// boolean center = (keyCode == KeyEvent.KEYCODE_DPAD_UP);

			if (okStart && mMode == STATE_WIN) {
				// ready-to-start -> start
				restart();
				return true;
			} else if (mMode == STATE_PAUSE && okStart) {
				// paused -> running
				unpause();
				return true;
			} else if (mMode == STATE_RUNNING) {
				if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
						|| keyCode == KeyEvent.KEYCODE_Q) {
					mPlayer.setTurning(Racer.TURN_LEFT);
					return true;
				} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
						|| keyCode == KeyEvent.KEYCODE_W) {
					mPlayer.setTurning(Racer.TURN_RIGHT);
					return true;
				} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
					mPlayer.setAccelerating(Racer.ACCELERATE);					
					return true;
				} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
					mPlayer.setAccelerating(Racer.DECCELERATE);	
					return true;
				}
			}

			return false;
		//}
	}

	/**
	 * Handles a key-up event.
	 * 
	 * @param keyCode
	 *            the key that was pressed
	 * @param msg
	 *            the original event object
	 * @return true if the key was handled and consumed, or else false
	 */
	boolean doKeyUp(int keyCode, KeyEvent msg) {
		boolean handled = false;

		//synchronized (mSurfaceHolder) {
			if (mMode == STATE_RUNNING) {
				if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
						|| keyCode == KeyEvent.KEYCODE_Q
						|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
						|| keyCode == KeyEvent.KEYCODE_W) {
					mPlayer.setTurning(Racer.TURN_NONE);
					handled = true;
				} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP
						|| keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
					mPlayer.setAccelerating(Racer.STOPPED);
					handled = true;
				}
			}
		//}

		return handled;
	}
		
	//Needed to supply the view touch handler of the position of the co-ordinates
	public Rect getControlsBitmapRect() {
		return mControlsRect;
	}
		
	private void nextLevel() {

		setState(STATE_PAUSE);
		
		//Stop moving
		mPlayer.stop();
		mRival.stop();
		
		//Notify up so we can get a dialog displayed
		Message msg = mHandler.obtainMessage();
		Bundle b = new Bundle();
		b.putBoolean("levelComplete", true);
		b.putInt("levelNumber", mRaceNumber);
		b.putInt("position", mRacePosition);
		b.putCharSequence("time", mCurrentTimeString);
		msg.setData(b);
		mHandler.sendMessage(msg);
		

		
	}

	/***
	 * Called when the activity is destroyed - recycle all the bitmaps
	 */
	public void onDestroy() {
        
		synchronized (mSurfaceHolder) {
	        //No longer run the thread
	        mRun = false;
	        mControlsBitmap.recycle();
	        mControlsBitmap = null;
	        mSpritesBitmap.recycle();
	        mSpritesBitmap = null;
	        mSprites2Bitmap.recycle();
	        mSpritesBitmap = null;
	        mBgBitmap.recycle();
	        mBgBitmap = null;
		}
        
	}

	public int getRaceNumber() {
		return mRaceNumber;
	}

	public void moveToNextLevel() {

		mRaceNumber++;
		mRacePosition = 1;
		mLastPlayerDelta = 0;
		
		mPlayer.reset();
		mRival.reset();
		
		mStartTime = new Date();
		
		mRoadParam.reset();
		
		//Create the next level
		generateRoad();

		setState(STATE_RUNNING);
		
	}

}// End RacersThread class
